package de.dnb.gnd.utils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;

import de.dnb.basics.applicationComponents.ListUtils;
import de.dnb.basics.applicationComponents.Pair;
import de.dnb.basics.applicationComponents.RangeCheckUtils;
import de.dnb.basics.statistics.ListMultimap;
import de.dnb.basics.statistics.Multimap;
import de.dnb.gnd.parser.Record;
import de.dnb.gnd.parser.line.Line;
import de.dnb.gnd.parser.tag.BibTagDB;

/**
 * Utility-Klasse für Sacherschließungssegment am Titeldatensatz.
 * 
 * @author baumann
 *
 */
public final class SubjectUtils {

    private SubjectUtils() {
    }

    private static final BibTagDB TAG_DB = BibTagDB.getDB();

    public static Collection<Line> getDDCSegment(Record record) {
        return RecordUtils.getLinesBetween(record, "5400", "5444");
    }

    /**
     * Notation ohne Präfix -Tx--.
     * @param record	nicht null
     * @return			nicht null
     */
    public static List<String> getTable1Notations(Record record) {
        Collection<Line> lines = getAuxiliarTables(record);
        return SubfieldUtils.getContentsOfFirstSubfields(lines, 'f');
    }

    /**
     * Notation ohne Präfix -Tx--.
     * @param record	nicht null
     * @return			nicht null
     */
    public static List<String> getTable2Notations(Record record) {
        Collection<Line> lines = getAuxiliarTables(record);
        return SubfieldUtils.getContentsOfFirstSubfields(lines, 'g');
    }

    /**
     * Notation ohne Präfix -Tx--.
     * @param record	nicht null
     * @return			nicht null
     */
    public static List<String> getTable3ANotations(Record record) {
        Collection<Line> lines = getAuxiliarTables(record);
        return SubfieldUtils.getContentsOfFirstSubfields(lines, 'h');
    }

    /**
     * Notation ohne Präfix -Tx--.
     * @param record	nicht null
     * @return			nicht null
     */
    public static List<String> getTable3BNotations(Record record) {
        Collection<Line> lines = getAuxiliarTables(record);
        return SubfieldUtils.getContentsOfFirstSubfields(lines, 'i');
    }

    /**
     * Notation ohne Präfix -Tx--.
     * @param record	nicht null
     * @return			nicht null
     */
    public static List<String> getTable3CNotations(Record record) {
        Collection<Line> lines = getAuxiliarTables(record);
        return SubfieldUtils.getContentsOfFirstSubfields(lines, 'j');
    }

    /**
     * Notation ohne Präfix -Tx--.
     * @param record	nicht null
     * @return			nicht null
     */
    public static List<String> getTable4Notations(Record record) {
        Collection<Line> lines = getAuxiliarTables(record);
        return SubfieldUtils.getContentsOfFirstSubfields(lines, 'k');
    }

    /**
     * Notation ohne Präfix -Tx--.
     * @param record	nicht null
     * @return			nicht null
     */
    public static List<String> getTable5Notations(Record record) {
        Collection<Line> lines = getAuxiliarTables(record);
        return SubfieldUtils.getContentsOfFirstSubfields(lines, 'l');
    }

    /**
     * Notation ohne Präfix -Tx--.
     * @param record	nicht null
     * @return			nicht null
     */
    public static List<String> getTable6Notations(Record record) {
        Collection<Line> lines = getAuxiliarTables(record);
        return SubfieldUtils.getContentsOfFirstSubfields(lines, 'm');
    }

    /**
     * Liefert die Hilfstafeln.
     * 
     * @param record nicht null
     * @return
     */
    public static ArrayList<Line> getAuxiliarTables(Record record) {
        Objects.requireNonNull(record);
        return RecordUtils.getLines(record, "5403", "5413", "5423", "5433",
            "5443");
    }

    /**
     * Liefert die Zeilen mit vollständigen DDC-Notationen.
     * 
     * @param record nicht null
     * @return		nicht null
     */
    public static ArrayList<Line> getCompleteDDCNotationLines(
        final Record record) {
        Objects.requireNonNull(record);
        return RecordUtils.getLines(record, "5400", "5410", "5420", "5430",
            "5440");
    }

    /**
     * Gibt die Anzahl der vergebenen vollständigen DDC-Notationen, also in
     * 54n0 die  Zahl n + 1. Wenn  der Datensatz bearbeitet wird, kann es
     * sein, dass noch nicht alle 54nX-Felder nelegt sind. Dann wird die
     * höchste 54nX-Nummer herangezogen.
     * 
     * @param record    nicht null
     * @return          Zahl
     */
    public static int getNumberOfDDCNotations(final Record record) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        Collection<Line> lines = SubjectUtils.getDDCSegment(record);
        Collection<String> list = RecordUtils.getTags(lines);
        if (list.size() == 0)
            return 0;
        String max = ListUtils.getMax(list);
        return max.charAt(2) - '0' + 1;
    }

    /**
     * Liefert die Zeilen mit DDC-Grundnotationen.
     * 
     * @param record nicht null
     * @return		nicht null
     */
    public static ArrayList<Line> getDDCMainScheduleLines(final Record record) {
        Objects.requireNonNull(record);
        return RecordUtils.getLines(record, "5401", "5411", "5421", "5431",
            "5441");
    }

    /**
     * Liefert alle DDC-Grundnotationen.
     * 
     * @param record	nicht null
     * @return			nicht null
     */
    public static List<String> getDDCMainScheduleNotations(final Record record) {
        Collection<Line> lines = getDDCMainScheduleLines(record);
        return SubfieldUtils.getContentsOfFirstSubfields(lines, 'a');
    }

    /**
     * Liefert die Zeilen mit DDC-Notationen anderer Haupttafeln.
     * 
     * @param record nicht null
     * @return		nicht null
     */
    public static ArrayList<Line> getDDCOtherScheduleLines(final Record record) {
        Objects.requireNonNull(record);
        return RecordUtils.getLines(record, "5402", "5412", "5422", "5432",
            "5442");
    }

    /**
     * Liefert alle DDC-Notationen anderer Haupttafeln.
     * 
     * @param record	nicht null
     * @return			nicht null
     */
    public static
        List<String>
        getDDCOtherScheduleNotations(final Record record) {
        Collection<Line> lines = getDDCOtherScheduleLines(record);
        return SubfieldUtils.getContentsOfFirstSubfields(lines, 'a');
    }

    /**
     * Liefert alle vollständigen DDC-Notationen.
     * 
     * @param record	nicht null
     * @return			nicht null
     */
    public static List<String> getCompleteDDCNotations(final Record record) {
        Collection<Line> lines = getCompleteDDCNotationLines(record);
        return SubfieldUtils.getContentsOfFirstSubfields(lines, 'a');
    }

    /**
     * Die Zeilen mit den Sachgruppen (5050).
     * 
     * @param record	nicht null
     * @return			nicht null, eventuell leer
     */
    public static Collection<Line> getSubjectGroupLines(Record record) {
        Objects.requireNonNull(record);
        return RecordUtils.getLines(record, "5050");
    }

    /**
     * Die Zeilen mit RSWK-Schlagwörtern.
     * 
     * @param record	nicht null
     * @return			nicht null, eventuell leer
     */
    public static Collection<Line> getRSWKLines(final Record record) {
        Objects.requireNonNull(record);
        return RecordUtils.getLinesBetween(record, "5100", "5195");
    }

    /**
     * Gibt ein Set mit den ids aller Schlagwörter, die am Titel hängen.
     * 
     * @param record	nicht null
     * @return			nicht null, eventuell leer, keine Doppelten
     */
    public static Collection<String> getRSWKids(final Record record) {
        Objects.requireNonNull(record);
        Collection<Line> rswkLines = getRSWKLines(record);
        Collection<String> rswkIDs =
            SubfieldUtils.getContentsOfFirstSubfields(rswkLines, '9');
        return new LinkedHashSet<>(rswkIDs);
    }

    /**
     * Gibt die DDC-Hauptsachgruppe eines Datensatzes.
     * 
     * @param record	nicht null
     * @return			null, wenn nichts gefunden
     */
    public static String getDHS(final Record record) {
        Objects.requireNonNull(record);
        Collection<Line> lines5050 = getSubjectGroupLines(record);
        if (lines5050.isEmpty())
            return null;
        Multimap<String, String> type2SG = new ListMultimap<>();
        for (Line line : lines5050) {
            // Erfassungsart
            String type = SubfieldUtils.getContentOfFirstSubfield(line, 'E');
            String sg = SubfieldUtils.getContentOfFirstSubfield(line, 'e');
            if (sg != null)
                type2SG.add(type, sg);
        }
        Collection<String> hsgs;

        // von Hand eingegeben, ohne Typ in $E
        hsgs = type2SG.get(null);
        if (hsgs != null)
            return hsgs.iterator().next();

        // "intellektuell
        hsgs = type2SG.get("i");
        if (hsgs != null)
            return hsgs.iterator().next();

        // Parallelausgabe
        hsgs = type2SG.get("p");
        if (hsgs != null)
            return hsgs.iterator().next();

        // maschinell
        hsgs = type2SG.get("m");
        if (hsgs != null)
            return hsgs.iterator().next();

        // Ablieferung
        hsgs = type2SG.get("a");
        if (hsgs != null)
            return hsgs.iterator().next();

        // unbekannter Typ in $E
        Collection<String> flat = type2SG.flatten();
        // alte HSG:
        if (flat.isEmpty())
            return null;

        return flat.iterator().next();

    }

    /**
     * Die Bearbeitungszeit eines Datensatzes.
     * @param record nicht null
     * @return	Die Bearbeitungszeit eines Datensatzes in Minuten: 
     * 
     * <br><br>
     * RSWK und DDC Erschließung: 20 min 
     * (Reihe A ohne Sachgruppen B, K, S)<br>
     * 
     * nur RSWK Erschließung: 15 min 
     * (Reihe A der Sachgruppen K, S und B in Auswahl)<br>
     * 
     * nur DDC Erschließung: 10 min (Reihe B und H ohne Sachgruppen B, K, S)<br>
     * 
     * Erschließung nur mit der Sachgruppe oder Kurz-DDC: 5 min 
     * (Reihe H bei der Sachgruppe 610; Veröffentlichungen, 
     * die zur Stufe 3 der inhaltlichen Erschließung gehören)

     */
    public static int getProcessingTime(Record record) {
        STATUS status = getErschliessungsStatus(record);
        if (status != null)
            return status.processingTime;
        return 0;

    }

    public static enum STATUS {
        DHS("nur Sachgruppe", 5), SWW("nur Schlagwörter", 15), DDC("nur DDC",
            10), FULL("SWW und DDC", 20);

        public String verbal;

        public int processingTime;

        private STATUS(String verbal, int processingTime) {
            this.verbal = verbal;
            this.processingTime = processingTime;
        }
    };

    /**
     * Gibt den Status (SWW, DDC, DHS).
     * 
     * 
     * @param record	nicht null
     * @return			Erschließungsstatus oder null
     */
    public static STATUS getErschliessungsStatus(Record record) {
        if (containsDDC(record) && containsSWW(record))
            return STATUS.FULL;
        if (containsSWW(record) && !containsDDC(record))
            return STATUS.SWW;
        if (containsDDC(record) && !containsSWW(record))
            return STATUS.DDC;
        if (containsDHS(record))
            return STATUS.DHS;
        return null;

    }

    public static boolean containsDHS(Record record) {
        return RecordUtils.containsField(record, "5050");
    }

    public static boolean containsSWW(Record record) {
        return RecordUtils.containsField(record, "5100");
    }

    public static boolean containsDDC(Record record) {
        return RecordUtils.containsField(record, "5400");
    }

    /**
     * Gibt alle DDC-Notationen am Titel, egal, ob synthetisiert oder
     * nicht; die Hilfstafeln mit dem korrekten Präfix -TX--.
     * 
     * @param record	nicht null
     * @return			nicht null
     */
    public static Set<String> getAllDDCNotations(final Record record) {
        Objects.requireNonNull(record);
        List<Line> lines = new LinkedList<>();
        lines.addAll(getDDCMainScheduleLines(record));
        lines.addAll(getDDCOtherScheduleLines(record));
        lines.addAll(getAuxiliarTables(record));
        Set<String> ddcs = new LinkedHashSet<>();
        ddcs.addAll(getCompleteDDCNotations(record));
        for (Line line : lines) {
            ddcs.add(RecordUtils.toPicaWithoutTag(line));
        }

        return ddcs;
    }

    public static void main(String[] args) {
        Record record = RecordUtils.readFromClip();
        System.out.println(getNumberOfDDCNotations(record));

    }

}
